Что такое const type parameters в TypeScript?
const перед параметром дженерика (<const T>), который заставляет TypeScript выводить максимально узкий (литеральный, неизменяемый) тип аргумента вместо его расширения до общего типа, как если бы аргумент был помечен as const.Проблема, которую решает const type parameters
До TypeScript 5.0 при передаче литералов в дженерик-функцию компилятор автоматически «расширял» (widening) их типы до более общих. Массив строковых литералов превращался в string[], а объектный литерал с конкретным значением поля — в тип с широким полем вроде string.
function getNames<T extends readonly string[]>(arr: T): T {
return arr;
}
const result = getNames(["Alice", "Bob"]);
// result: string[] — литеральная информация потеряна
Чтобы сохранить точные литеральные типы, раньше приходилось просить вызывающий код добавлять as const, но это легко забыть, и заставить это делать принудительно было нельзя.
Как работает const type parameters
С TypeScript 5.0 перед параметром типа можно поставить модификатор const:
function getNamesExactly<const T extends readonly string[]>(arr: T): T {
return arr;
}
const result = getNamesExactly(["Alice", "Bob"]);
// result: readonly ["Alice", "Bob"]
Теперь компилятор выводит тип аргумента так, как будто вызывающая сторона написала as const: массивы становятся кортежами из литералов и помечаются readonly, а свойства объектов получают самые узкие возможные литеральные типы. Это происходит автоматически, без изменений на стороне вызывающего кода.
Важные нюансы
constв дженерике влияет только на вывод типа аргумента при вызове функции — сам параметр типа в остальном ведёт себя как обычный.- Ограничение (
extends) по-прежнему проверяется: если указаноT extends readonly string[], аргумент всё равно должен быть массивом строк. - Вызывающий код может явно расширить тип обратно, например указав переменную заранее с типом
string[], — модификаторconstне запрещает это, а лишь меняет поведение вывода по умолчанию. - Модификатор применяется к параметру типа функции, метода или класса, а не к обычным переменным — не путать с ключевым словом
constдля объявления переменных.
Зачем это нужно на практике
Особенно полезно в API, которые строят более точные типы на основе переданных данных: конфигурации, построители маршрутов, схемы валидации, объекты с ключами-литералами для дальнейшего маппинга.
function createRoute<const T extends string>(path: T) {
return { path };
}
const route = createRoute("/users/:id");
// route.path имеет тип "/users/:id", а не string
Без const тип path расширился бы до string, и дальнейший анализ пути (например, извлечение параметров через template literal types) стал бы невозможен.
Отличие от readonly-модификатора и as const
const в дженерике — это про вывод типа при инференсе, а readonly в объявлении массива/кортежа — про неизменяемость на уровне типа. as const решает похожую задачу, но требует явного указания на стороне вызова. const T переносит эту ответственность на автора API, гарантируя узкий вывод для всех вызовов без исключений.
Что хочет услышать интервьюер
Кандидат объясняет проблему widening (расширения типов) литералов в обычных дженериках
Кандидат знает синтаксис `<const T extends ...>` и с какой версии TypeScript он появился (5.0)
Кандидат понимает, что это эквивалент неявного `as const` на стороне вызова, а не изменение семантики самого параметра
Кандидат приводит практический пример использования (конфигурации, построители маршрутов, литеральные ключи объектов)
Кандидат отличает `const` type parameter от обычного `readonly` и от `as const`
Пример: Без const: тип расширяется
function getNames<T extends readonly string[]>(arr: T): T {
return arr;
}
const result = getNames(["Alice", "Bob"]);
// result: string[]
Пример: С const: тип сохраняется литеральным
function getNamesExactly<const T extends readonly string[]>(arr: T): T {
return arr;
}
const result = getNamesExactly(["Alice", "Bob"]);
// result: readonly ["Alice", "Bob"]
Пример: Практический пример: точный тип пути
function createRoute<const T extends string>(path: T) {
return { path };
}
const route = createRoute("/users/:id");
// route.path: "/users/:id", а не string
Типичные ошибки
Путают `const` перед параметром типа с объявлением константы `const` для переменных
Считают, что `const` меняет ограничение (`extends`) параметра, хотя оно проверяется как обычно
Не могут объяснить, зачем это нужно, если есть `as const` — не видят разницы между ответственностью вызывающего и автора API
Забывают, что результат становится `readonly`-структурой, и потом пытаются мутировать полученный массив/объект
Не знают минимальную версию TypeScript (5.0), в которой появилась эта возможность


